package net.pulseproject.router.modules.useable;

import java.util.Collection;

import javax.sound.midi.ShortMessage;

import net.pulseproject.commons.midi.Receivable;
import net.pulseproject.commons.midi.Transmitable;
import net.pulseproject.commons.midi.entity.MidiMessageStatus;
import net.pulseproject.commons.util.MidiUtil;
import net.pulseproject.router.modules.BiDirectionalModule;
import net.pulseproject.router.modules.logic.ValueAdderImpl;
import net.pulseproject.router.modules.logic.ValueModifier;
import net.pulseproject.router.modules.logic.valuethreshold.MessageBroadcaster;
import net.pulseproject.router.modules.logic.valuethreshold.MultipleMessageSender;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class ValueThresholdModule extends BiDirectionalModule implements MessageBroadcaster {

	private static final Log LOG = LogFactory.getLog(ValueThresholdModule.class);

	private final ValueModifier<Transmitable> adder;
	
	/** Constructor argument. */
	private final int lowerThresholdTrigger;

	/** Constructor argument. */
	private final int upperThresholdTrigger;

	/** Constructor argument. */
	private final int startValue;

	/** Constructor argument. */
	private final int endValue;

	/** Constructor argument. */
	private final int transformationTimeInMs;

	private MultipleMessageSender lowerSender;

	private MultipleMessageSender upperSender;

	private int recentSum = 0;
	
	private boolean wasRecentlyGoingDown = false;
	
	private boolean wasRecentlyGoingUp = false;
	
	
	/**
	 * @param id unique module ID.
	 * @param outgoingModules self explanatory.
	 * @param lowerThresholdTrigger triggers reverse transformation when reached.
	 * @param upperThresholdTrigger triggers transformation from start to end value.
	 * @param startValue first sent MIDI message starts with this value.
	 * @param endValue last sent MIDI message ends with this value.
	 * @param transformationTimeInMs time which should pass to send messages from start to end value.
	 * @param fanIn number of modules sending values into this module (calculated value, not an configuration option).
	 */
	public ValueThresholdModule(
			final String id,
			final Collection<Receivable> outgoingModules,
			final int lowerThresholdTrigger,
			final int upperThresholdTrigger,
			final int startValue,
			final int endValue,
			final int transformationTimeInMs,
			final int fanIn) {
		super(id, outgoingModules);
		LOG.debug("new ValueThresholdModule(id=[" + id + "], outgoingModules, ..., fanIn=" + fanIn + ")");
		
		this.adder = new ValueAdderImpl<Transmitable>(fanIn);
		
		if(lowerThresholdTrigger >= upperThresholdTrigger) {
			throw new IllegalArgumentException("lowerThresholdTrigger [" + lowerThresholdTrigger + "] >= " +
					"upperThresholdTrigger [" + upperThresholdTrigger + "]");
		}
		this.lowerThresholdTrigger = lowerThresholdTrigger;
		this.upperThresholdTrigger = upperThresholdTrigger;
		this.startValue = startValue;
		this.endValue = endValue;
		this.transformationTimeInMs = transformationTimeInMs;
	}
	
	@Override
	public final void receive(final ShortMessage message, final Transmitable transmitable) {
		LOG.trace("receive(message, transmitable)");
		
		if(MidiMessageStatus.isNoteMessage(message) == true) {
			throw new RuntimeException("Received a note message, " +
					"but only controls are allowed: " + MidiUtil.toString(message));
		}
		
		final int sum = this.adder.calculateValue(transmitable, message.getData2());
		
		LOG.trace("  sum=[" + sum + "], recentSum=[" + this.recentSum + "], wasRecentlyGoingUp=" +
				this.wasRecentlyGoingUp + ", wasRecentlyGoingDown=" + this.wasRecentlyGoingDown);
		
		if(this.shouldAbort(sum) == true) {
			return;
		}
		
		this.checkStateAndStopIfNecessary(sum);
		
		if(this.recentSum < this.upperThresholdTrigger &&
				sum >= this.upperThresholdTrigger &&
				this.wasRecentlyGoingUp == false) {
			
			LOG.debug("Starting upper sender as sum [" + sum + "] is higher than " +
					"upperThresholdTrigger [" + this.upperThresholdTrigger + "].");
			this.upperSender = new MultipleMessageSender(this, this.startValue, this.endValue,
					this.transformationTimeInMs);
			this.upperSender.start();
			
			this.wasRecentlyGoingUp = true;
			this.wasRecentlyGoingDown = false;
			
		} else if(this.recentSum > this.lowerThresholdTrigger &&
				sum <= this.lowerThresholdTrigger &&
				this.wasRecentlyGoingDown == false) {
			
			LOG.debug("Starting lower sender as sum [" + sum + "] is lower than " +
					"lowerThresholdTrigger [" + this.lowerThresholdTrigger + "].");
			this.lowerSender = new MultipleMessageSender(this, this.endValue, this.startValue,
					this.transformationTimeInMs);
			this.lowerSender.start();
			
			this.wasRecentlyGoingUp = false;
			this.wasRecentlyGoingDown = true;
		}
		
		this.recentSum = sum;
	}
	
	private boolean shouldAbort(final int sum) {
		if(sum >= this.upperThresholdTrigger && this.upperSender != null && this.upperSender.isRunning()) {
			LOG.trace("Aborting, as upper already is running.");
			return true;
		}
		
		if(sum <= this.lowerThresholdTrigger && this.lowerSender != null && this.lowerSender.isRunning()) {
			LOG.trace("Aborting, as lower already is running.");
			return true;
		}
		
		return false;
	}
	
	private void checkStateAndStopIfNecessary(final int sum) {
		if(sum >= this.upperThresholdTrigger && this.lowerSender != null) {
			if(this.lowerSender.isRunning() == true) {
				this.lowerSender.stop();
			}
			this.lowerSender = null;
		}
		
		if(sum <= this.lowerThresholdTrigger && this.upperSender != null) {
			if(this.upperSender.isRunning() == true) {
				this.upperSender.stop();
			}
			this.upperSender = null;
		}
	}

	@Override
	public final void broadcast(final ShortMessage message) {
		if(message == null) { throw new IllegalArgumentException("message == null"); }
		this.broadcastToOutgoingModules(message);		
	}
	
}
